﻿/*
	[ru]	Пример использования тесселяции для ландшафта.
			Используется:
				- материалы
				- генерация геометрии
				- камера
				- профайлинг
			Управлние:
				W A S D SHIFT SPACE	- движение
				R					- перезагрузить материал
				P					- включить/отключить паказ сетки
				LCTRL				- переключить тип камеры (FPS/Flight)
				Right Mouse Button	- включить/отключить камеру
				Mouse Wheel			- изменить увеличение
				Middle Mouse Button	- отключить увеличение
				+ -					- изменить уровень тесселяции
				[ ]					- изменить высоту
				< >					- изменить уровень детализации
*/

#include "Engine.h"
using namespace Engine;


#pragma pack(push,1)
struct TMaterialData
{
	vec3	vLightNormal;
	float	fHeightScale;
	float	fDetailLevel;
	float	fMaxLevel;
	float	fQuadSize;
	float	__empty;
};
#pragma pack(pop)


class CExample : public ICoreEventListener
{
private:
	CRenderQueue			cRQ;
	CInputCameraComp		cCamera;
	IModelPtr				pTerrain,
							pSkyBox;
	TMaterialData	*		pMaterialData;
	CFontMaterial			cFont;
	string					sProfilerText;
	float					fLastTime;
	bool					bIsCameraActive;
	bool					bWire;

	const int				iGridSize;

public:
	CExample(): fLastTime(0), bIsCameraActive(true), bWire(false), pMaterialData(nullptr), iGridSize(60)
	{}

	~CExample()
	{}
	
	void Draw();
	void Process();

	virtual void OnInit();
	virtual void OnFree();

	virtual void OnUpdate();

	virtual void Release()									{ delete this; }
	virtual e_win_event::type GetEvents()					{ return e_win_event::UNKNOWN; }

	virtual void ProcessMessage(const TWindowEvent &sEvent)	{}
	virtual void OnException()								{}
	
	static int s_RunEngine();
};




inline void CExample::Draw()
{
	RENDER()->GetProfiler()->BeginFrame();
	RENDERGL()->ClearBuffers();
	const uint	u_drawing = RENDER()->GetProfiler()->BeginProfiling( "drawing" );

	TCameraRenderQueue	*p_crq = nullptr;
	
	// build render queue
	if ( cRQ.GetRenderQueue( &cCamera, p_crq ) )
	{
		const frustum_t *	p_frustum;
		vec3				v_pos;
		vec2				v_clip;
		vec3 const			v_center = (vec3( pMaterialData->fQuadSize * (float)iGridSize ) *
										vec3( 0.5f, 0.2f, 1.f )) + vec3(0.f, 0.f, 50.f);

		p_crq->Camera()->GetParameters( nullptr, nullptr, &p_frustum, nullptr, &v_pos, &v_clip, nullptr );
		p_crq->ClearMem();

		pSkyBox->Render( IModel::TROParams( *p_frustum, mat4::Identity(), v_clip.y - v_clip.x ), p_crq );
		pTerrain->Render( IModel::TROParams(	*p_frustum,  mat4::Translate( v_pos - v_center ),
											v_clip.y - v_clip.x ), p_crq );
	}

	// render
	if ( bWire )	SMAN()->PolygonMode( gl_polygon_mode::LINE );
	else			SMAN()->PolygonMode( gl_polygon_mode::FILL );

	RENDER()->GetRenderTech()->Render( &cRQ, 0 );
	
	SMAN()->PolygonMode( gl_polygon_mode::FILL );
	
	
	// profiling //
	RENDERGL()->ClearDepth();
	MSTACK()->SetMatrix( mat4::BuildOrtho2D( frect(-1.f, -1.f, 1.f, 1.f) ), e_transform::PROJECTION );
	MSTACK()->SetMatrix( mat4::Identity(), e_transform::VIEW );
	MSTACK()->SetMatrix( mat4::Identity(), e_transform::MODEL );

	SMAN()->Enable( e_gl_state::BLEND );

	cFont.DrawMultiline( frect(-1.f, -1.f, 1.f, 1.f), sProfilerText, color4f( 0.8f, 0.4f, 0.f, 1.f ) );
	BATCH()->Flush();
	BATCH()->Clear();

	SMAN()->Disable( e_gl_state::BLEND );

	RENDER()->GetProfiler()->EndFrame();
	RENDER()->GetProfiler()->EndProfiling( u_drawing );

	RENDER()->GetProfiler()->GetProfilerText( sProfilerText );
	RENDER()->GetProfiler()->Reset();
	

	RENDERGL()->SwapBuffers();
}


inline void CExample::Process()
{
	INPUT()->Update();

	if ( INPUT()->IsKey(e_key::ESCAPE) )
		CORE()->QuitEngine();

	if ( INPUT()->IsMBClick(e_mouse::RIGHT) )
		bIsCameraActive = !bIsCameraActive;

	if ( bIsCameraActive )
	{
		TSNCompMessage	msg;
		msg.Update( nullptr, CORE()->GetUpdateTimeDelta() );

		cCamera.ProcessMessage( msg );
		
		INPUT()->CursorToCenter();
		INPUT()->SetParams( e_input_flags::HIDE_CURSOR | e_input_flags::EXCLUSIVE );
	}
	else
		INPUT()->SetParams( e_input_flags::UNKNOWN );


	if ( INPUT()->IsKeyClick(e_key::P) )
		bWire = !bWire;
	
	if ( INPUT()->IsKeyClick(e_key::PLUS) )			++pMaterialData->fMaxLevel;				else
	if ( INPUT()->IsKeyClick(e_key::MINUS) &&
		 pMaterialData->fMaxLevel > 1.f )			--pMaterialData->fMaxLevel;

	if ( INPUT()->IsKeyClick(e_key::RBRACKET) )		pMaterialData->fHeightScale -= 2.f;		else
	if ( INPUT()->IsKeyClick(e_key::LBRACKET) )		pMaterialData->fHeightScale += 2.f;	

	if ( INPUT()->IsKeyClick(e_key::COMMA) &&
		 pMaterialData->fDetailLevel > 10.f )		pMaterialData->fDetailLevel -= 10.f;		else
	if ( INPUT()->IsKeyClick(e_key::PERIOD) )		pMaterialData->fDetailLevel += 10.f;

	INPUT()->Reset();
}


void CExample::OnInit()
{
	bool				ret		= true;
	IRenderTechnique *	p_tech	= nullptr;

	ret &= RENDER()->GetRTManager()->CreateTechnique( p_tech, e_technique::FORWARD_LIGHTING );
	RENDER()->SetRenderTechnique( p_tech );
	
	ret &= RESMAN()->Create( e_resource::MULTIMESH_MODEL, pSkyBox );
	ret &= RESMAN()->Create( e_resource::MULTIMESH_MODEL, pTerrain );
	

	// create skybox //
	{
		TMultiMesh		s_mesh;
		IMaterialPtr	p_mtr;
		CMultiMeshPtr	p_mesh;

		ret &= RESMAN()->Load( e_resource::MATERIAL, p_mtr, "skybox.ocf" );
		ret &= RESMAN()->Create( e_resource::MULTIMESH, p_mesh );
		
		if ( Geometry::CreateCubeCM( s_mesh, 100.f ) )
			ret &= p_mesh->Create( s_mesh, e_mesh_flags::STATIC );

		ret &= ((CMultiMeshModelPtr)pSkyBox)->Create( (CMultiMeshPtr)p_mesh, uni_array<IMaterialPtr>( &p_mtr, 1 ) );
	}


	// create terrain //
	{
		TMultiMesh					s_mesh;
		IMaterialPtr				p_mtr;
		CMultiMeshPtr				p_mesh;
		IMaterial::progmtr_array_t	a_mtrs;

		ret &= RESMAN()->Load( e_resource::MATERIAL, p_mtr, "displacement.ocf" );

		p_mtr->GetUniqueMaterials( a_mtrs );

		if ( !a_mtrs.Empty() )
		{
			TProgramData		s_data;
			IProgramMaterial *	p_prog_mtr = a_mtrs.Front();

			ret &= p_prog_mtr->GetMaterialData( s_data );

			ASSERT( sizeof(TMaterialData) == s_data.uSize );
			pMaterialData = (TMaterialData *)s_data.pData;	// it's hack, don't repeat this
		}
	
		if ( Geometry::CreateGrid( s_mesh, iGridSize, pMaterialData->fQuadSize, 3 ) )
		{
			ret &= RESMAN()->Create( e_resource::MULTIMESH, p_mesh );
			ret &= p_mesh->Create( s_mesh, e_mesh_flags::STATIC );
			s_mesh.Clear();
		}

		ret &= ((CMultiMeshModelPtr)pTerrain)->Create( (CMultiMeshPtr)p_mesh, uni_array<IMaterialPtr>( &p_mtr, 1 ) );
	}

	
	// init font //
	cFont.Load( "LucidaConsole.fnt", e_def_program::FONT_GS );
	cFont.SetPrimitiveType( gl_primitive::POINT );
	cFont.SetScale( 0.04f );


	// init profiler //
	RENDER()->GetProfiler()->Init();


	RENDERGL()->SetClearColor( color4f(0.3f) );
	RENDERGL()->SetClearDepth( 1.f );

	SMAN()->Enable( e_gl_state::DEPTH_TEST );
	SMAN()->Disable( e_gl_state::CULL_FACE );
	
	SMAN()->BlendFunc( gl_blend_func::SRC_ALPHA, gl_blend_func::ONE_MINUS_SRC_ALPHA );

	// init camera //
	cCamera.SetType( CInputCameraComp::CT_FPS );
	cCamera.Create( vec3(), vec3(), vec2(1.f, 5000.f), 0.5f*0.78f );
	cCamera.SetSens( 0.07f, vec3(1.f), 0.02f, e_vec_swizzle::DEFAULT );
	/*
	cCamera.SetType( CInputCameraComp::CT_FLIGHT );
	cCamera.Create( vec3(), vec3(), vec2(1.f, 5000.f), 0.5f*0.78f );
	cCamera.SetSens( 0.07f, vec3(1.f, -1.f, 1.f), 0.02f, e_vec_swizzle::Z___ | e_vec_swizzle::_Y__ | e_vec_swizzle::__X_ );
	*/
	TCameraRenderQueue *	p_crq = new TCameraRenderQueue();

	ret &= p_crq->Create( & cCamera, e_render_layer::ALL_BITS, e_renqueue_flags::UNKNOWN );

	cRQ.AddCamera( p_crq );

	ASSERT( ret );
}


void CExample::OnFree()
{
}


void CExample::OnUpdate()
{
	CORE()->Update();
	Draw();
	
	fLastTime += CORE()->GetUpdateTimeDelta();

	if ( fLastTime > 1.f/60.f )
	{
		fLastTime = 0.f;
		Process();
	}
}


int CExample::s_RunEngine()
{
	CExample	*		p_example			= new CExample();
	TEngInitParams		s_init_params;
	e_log_output::bits	e_log_bits			= e_log_output::LOG_FILE_BIT
											DEBUG_ONLY( | e_log_output::MESSAGE_BIT )
											;
	
	s_init_params.eFlags					= e_eng_init_flags::OPENGL_4_2
											| e_eng_init_flags::CORE_PROFILE
											| e_eng_init_flags::EXTENDED_CONTEXT
											//| e_eng_init_flags::DEBUG_MODE
											//| e_eng_init_flags::STREAMED_LOAD
											//| e_eng_init_flags::STEREO
											//| e_eng_init_flags::ES2_PROFILE
											;
	s_init_params.sApplicationName			= "Displacement mapping with tessellation";
	s_init_params.sClassName				= "";
	s_init_params.pHandle					= nullptr;
	s_init_params.pIcon						= nullptr;

	s_init_params.sAudioParams.sAudioDevice	= "";

	s_init_params.sRenInit.uColorDepth		= 32;
	s_init_params.sRenInit.uZBufferDepth	= 24;
	s_init_params.sRenInit.uStencilDepth	= 0;
	s_init_params.sRenInit.uColorSamples	= 0;
	s_init_params.sRenInit.uCSAASamples		= 0;
	s_init_params.sRenInit.uMSAASamples		= 0;
	s_init_params.sRenInit.uPixelFormat		= 0;

	s_init_params.sRenParams.bFullScreen	= false;
	s_init_params.sRenParams.bVSync			= true;
	s_init_params.sRenParams.eMode			= e_window_mode::ALLOW_SIZING | e_window_mode::CENTERED;
	s_init_params.sRenParams.sWinPos		= ivec2( 0, 0 );
	s_init_params.sRenParams.sWinSize		= ivec2( 640, 480 );
	
	s_init_params.sExtParams.uLogLevel		= 15;
	s_init_params.sExtParams.eLogFilter		= e_log_msg::FILTER_ALL;
	s_init_params.sExtParams.eResManFlags	= e_resman_flags::FREE_NON_REF | e_resman_flags::SET_DEFAUL_RES;
	s_init_params.sExtParams.sResDirectory	= "data";
	s_init_params.uLoadThreads				= 0;
	s_init_params.uRenderThreads			= 0;
	
	if ( !RunEngine( p_example, s_init_params, e_log_bits, "log.html", "data/config/settings.ocf" ) )
		return 1;

	return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	return CExample::s_RunEngine();
}